// Copyright 2023 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <common/demangle.h>

#include <iomanip>
#include <tuple>
#include <type_traits>

/** Usage:
  *
  * DUMP(variable...)
  */


template <typename Out, typename T>
Out & dumpValue(Out &, T &&);


/// Catch-all case.
template <int priority, typename Out, typename T>
std::enable_if_t<priority == -1, Out> & dumpImpl(Out & out, T &&)
{
    return out << "{...}";
}

/// An object, that could be output with operator <<.
template <int priority, typename Out, typename T>
std::enable_if_t<priority == 0, Out> & dumpImpl(
    Out & out,
    T && x,
    std::decay_t<decltype(std::declval<Out &>() << std::declval<T>())> * = nullptr)
{
    return out << x;
}

/// A pointer-like object.
template <int priority, typename Out, typename T>
std::enable_if_t<
    priority == 1
        /// Protect from the case when operator * do effectively nothing (function pointer).
        && !std::is_same_v<std::decay_t<T>, std::decay_t<decltype(*std::declval<T>())>>,
    Out> &
dumpImpl(Out & out, T && x, std::decay_t<decltype(*std::declval<T>())> * = nullptr)
{
    if (!x)
        return out << "nullptr";
    return dumpValue(out, *x);
}

/// Container.
template <int priority, typename Out, typename T>
std::enable_if_t<priority == 2, Out> & dumpImpl(
    Out & out,
    T && x,
    std::decay_t<decltype(std::begin(std::declval<T>()))> * = nullptr)
{
    bool first = true;
    out << "{";
    for (const auto & elem : x)
    {
        if (first)
            first = false;
        else
            out << ", ";
        dumpValue(out, elem);
    }
    return out << "}";
}


/// string and const char * - output not as container or pointer.

template <int priority, typename Out, typename T>
std::enable_if_t<priority == 3 && (std::is_same_v<std::decay_t<T>, std::string> || std::is_same_v<std::decay_t<T>, const char *>), Out> & dumpImpl(
    Out & out,
    T && x)
{
    return out << std::quoted(x);
}

/// UInt8 - output as number, not char.

template <int priority, typename Out, typename T>
std::enable_if_t<priority == 3 && std::is_same_v<std::decay_t<T>, unsigned char>, Out> & dumpImpl(Out & out, T && x)
{
    return out << int(x);
}


/// Tuple, pair
template <size_t N, typename Out, typename T>
Out & dumpTupleImpl(Out & out, T && x)
{
    if constexpr (N == 0)
        out << "{";
    else
        out << ", ";

    dumpValue(out, std::get<N>(x));

    if constexpr (N + 1 == std::tuple_size_v<std::decay_t<T>>)
        out << "}";
    else
        dumpTupleImpl<N + 1>(out, x);

    return out;
}

template <int priority, typename Out, typename T>
std::enable_if_t<priority == 4, Out> & dumpImpl(
    Out & out,
    T && x,
    std::decay_t<decltype(std::get<0>(std::declval<T>()))> * = nullptr)
{
    return dumpTupleImpl<0>(out, x);
}


template <int priority, typename Out, typename T>
Out & dumpDispatchPriorities(
    Out & out,
    T && x,
    std::decay_t<decltype(dumpImpl<priority>(std::declval<Out &>(), std::declval<T>()))> *)
{
    return dumpImpl<priority>(out, x);
}

struct LowPriority
{
    LowPriority(void *) {}
};

template <int priority, typename Out, typename T>
Out & dumpDispatchPriorities(Out & out, T && x, LowPriority)
{
    return dumpDispatchPriorities<priority - 1>(out, x, nullptr);
}


template <typename Out, typename T>
Out & dumpValue(Out & out, T && x)
{
    return dumpDispatchPriorities<5>(out, x, nullptr);
}


template <typename Out, typename T>
Out & dump(Out & out, const char * name, T && x)
{
    out << demangle(typeid(x).name()) << " " << name << " = ";
    return dumpValue(out, x);
}


#define DUMPVAR(VAR)              \
    dump(std::cerr, #VAR, (VAR)); \
    std::cerr << "; ";
#define DUMPHEAD std::cerr << __FILE__ << ':' << __LINE__ << " ";
#define DUMPTAIL std::cerr << '\n';

#define DUMP1(V1)                     \
    do                                \
    {                                 \
        DUMPHEAD DUMPVAR(V1) DUMPTAIL \
    } while (0);
#define DUMP2(V1, V2)                             \
    do                                            \
    {                                             \
        DUMPHEAD DUMPVAR(V1) DUMPVAR(V2) DUMPTAIL \
    } while (0);
#define DUMP3(V1, V2, V3)                                     \
    do                                                        \
    {                                                         \
        DUMPHEAD DUMPVAR(V1) DUMPVAR(V2) DUMPVAR(V3) DUMPTAIL \
    } while (0);
#define DUMP4(V1, V2, V3, V4)                                             \
    do                                                                    \
    {                                                                     \
        DUMPHEAD DUMPVAR(V1) DUMPVAR(V2) DUMPVAR(V3) DUMPVAR(V4) DUMPTAIL \
    } while (0);
#define DUMP5(V1, V2, V3, V4, V5)                                                     \
    do                                                                                \
    {                                                                                 \
        DUMPHEAD DUMPVAR(V1) DUMPVAR(V2) DUMPVAR(V3) DUMPVAR(V4) DUMPVAR(V5) DUMPTAIL \
    } while (0);

/// https://groups.google.com/forum/#!searchin/kona-dev/variadic$20macro%7Csort:date/kona-dev/XMA-lDOqtlI/GCzdfZsD41sJ

#define VA_NUM_ARGS_IMPL(x1, x2, x3, x4, x5, N, ...) N
#define VA_NUM_ARGS(...) VA_NUM_ARGS_IMPL(__VA_ARGS__, 5, 4, 3, 2, 1)

#define MAKE_VAR_MACRO_IMPL_CONCAT(PREFIX, NUM_ARGS) PREFIX##NUM_ARGS
#define MAKE_VAR_MACRO_IMPL(PREFIX, NUM_ARGS) MAKE_VAR_MACRO_IMPL_CONCAT(PREFIX, NUM_ARGS)
#define MAKE_VAR_MACRO(PREFIX, ...) MAKE_VAR_MACRO_IMPL(PREFIX, VA_NUM_ARGS(__VA_ARGS__))

#define DUMP(...) MAKE_VAR_MACRO(DUMP, __VA_ARGS__)(__VA_ARGS__)
